import {debounce} from 'lodash'
import * as fabric from 'fabric'

import BaseRender from './render'

export default class Render extends BaseRender {
  async render() {
    super.render()

    const {el} = this.scheme

    // 准备 canvas 容器
    let elCanvas = document.querySelector(el)
    if (!elCanvas) {
      elCanvas = document.createElement('canvas')
      document.body.appendChild(elCanvas)
    }
    const canvas = new fabric.Canvas(elCanvas, {preserveObjectStacking: true})

    // 解析 data 开始渲染
    if (this._autoSave && this._state) {
      // 检查存档
      await canvas.loadFromJSON(this._state)
      canvas.renderAll()
    } else {
      // 开始遍历 data 生成 fabric 元素并渲染到 canvas 上
      traverse(canvas, null, this.data)
    }

    // 定时存档
    this._autoSave && setInterval(() => this._save(canvas.toJSON()), 10 * 60 * 1000)

    // 检测鼠标移动则存档
    this._autoSave && document.addEventListener('mousemove',
      debounce(() => this._save(canvas.toJSON()), 100)
    )

    // 手动存档
    document.addEventListener('keydown', async e => {
      if (e.key === 's') {
        this._autoSave && this._save(canvas.toJSON())
      } else if (e.key === 'l') {
        await canvas.loadFromJSON(this._load())
      }
    })

    return this
  }
}

/**
 * 深度优先遍历 data，用于递归绘制元素在 canvas 上
 * @param canvas canvas 元素
 * @param parent 遍历路径上的节点前驱
 * @param data  遍历的数据
 */
function traverse(canvas, parent, data) {
  if (!canvas || !data.length) {
    return
  }

  const parentAbsPos = parent?.absPos || {top: 0, left: 0}
  for (let i = 0; i < data.length; i++) {
    const {type, children, ...args} = data[i]

    // 相对位置 => 绝对位置
    let {top = 0, left = 0} = args
    const absPos = {
      top: parentAbsPos.top + top,
      left: parentAbsPos.left + left,
    }
    Object.assign(args, absPos)

    // 创建节点
    const node = createFabricElement(type, args)
    node.absPos = absPos

    // 添加到到 canvas 上
    canvas.add(node)

    // 递归
    children && children.length && traverse(canvas, node, children)
  }
}

/**
 * 封装 fabric 的元素创建
 * @param type  元素的名称
 * @param args  创建 fabric 元素时需要需要传入的配置
 * @returns {*}
 */
function createFabricElement(type, args) {
  const typeName = type.slice(0, 1).toUpperCase() + type.slice(1)
  const constructor = fabric[typeName]

  return new constructor(args)
}
